/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package config;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;

/**
 *
 * @author Salm
 */
public class MapProcessor {
    private static final Set<String> MIN_MAX_KEYS =
            new HashSet(Arrays.asList(new String[]{"min", "max"}));
    
    private MapProcessor() {}
    
    public static Object minMaxRandom(Random randomer, Map<String, Object> pz)
    {
        if (MIN_MAX_KEYS.equals(pz.keySet()))
        {
            Object minVal = pz.get("min");
            Object maxVal = pz.get("max");
            
            if (Integer.class.isAssignableFrom(minVal.getClass()) &&
                Integer.class.isAssignableFrom(maxVal.getClass()))
            {
                int iMin = ((Number) minVal).intValue();
                int iMax = ((Number) maxVal).intValue();
                
                int ret = randomer.nextInt(iMax - iMin);
                ret += iMin;
                return ret;
            }
            else if (Long.class.isAssignableFrom(minVal.getClass()) &&
                Long.class.isAssignableFrom(maxVal.getClass()))
            {
                long lMin = ((Number) minVal).longValue();
                long lMax = ((Number) maxVal).longValue();
                
                long ret = Math.abs(randomer.nextLong()) % (lMax - lMin);
                ret += lMin;
                return ret;
            }
            else if (Double.class.isAssignableFrom(minVal.getClass()) &&
                Double.class.isAssignableFrom(maxVal.getClass()))
            {
                double dMin = ((Number) minVal).doubleValue();
                double dMax = ((Number) maxVal).doubleValue();
                
                double ret = Math.abs(randomer.nextDouble()) % (dMax - dMin);
                ret += dMin;
                return ret;
            }
            
            throw new IllegalArgumentException("Min max field must be number");
        }
        
        Map<String, Object> out = new HashMap(pz.size());
        for (Map.Entry<String, Object> pzEntry : pz.entrySet()) {
            String key = pzEntry.getKey();
            Object value = pzEntry.getValue();
            
            if (value instanceof Map)
            {
                out.put(key, minMaxRandom(randomer, (Map) value));
            }
            else
            {
                out.put(key, Cloner.clone(value));
            }
        }
        
        return out;
    }
    
    public static void sumAndMerge(Map<String, Object> sum, Map<String, Object> m)
    {
        for (Map.Entry<String, Object> entry : m.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            
            if (!sum.containsKey(key))
            {
                sum.put(key, Cloner.clone(value));
            }
            else
            {
                Object sumVal = sum.get(key);
                if (sumVal instanceof Map && value instanceof Map)
                {
                    sumAndMerge((Map) sumVal, (Map) value);
                    continue;
                }
                else if (sumVal instanceof Number)
                {
                    Number sumNum = null;
                    if (Integer.class.isAssignableFrom(sumVal.getClass()) &&
                        Integer.class.isAssignableFrom(value.getClass()))
                    {
                        // sum int
                        sumNum = ((Number) sumVal).intValue() + ((Number) value).intValue();
                    }
                    else if (Long.class.isAssignableFrom(sumVal.getClass()) &&
                        Long.class.isAssignableFrom(value.getClass()))
                    {
                        // sum long
                        sumNum = ((Number) sumVal).longValue() + ((Number) value).longValue();
                    }
                    else if (Double.class.isAssignableFrom(sumVal.getClass()) &&
                        Double.class.isAssignableFrom(value.getClass()))
                    {
                        // sum double
                        sumNum = ((Number) sumVal).doubleValue() + ((Number) value).doubleValue();
                    }
                    
                    if (sumNum != null)
                    {
                        sum.put(key, sumNum);
                        continue;
                    }
                }
                
                throw new IllegalArgumentException("Conflict key `" + key +
                        "` sum is " + sumVal.getClass() + " m is " + value.getClass());
            }
        }
    }
    
    public static Map<String, Object> sumAndMerge(Iterable<Map<String, Object>> maps)
    {
        Map<String, Object> out = new HashMap();
        for (Map<String, Object> map : maps) {
            sumAndMerge(out, map);
        }
        
        return out;
    }
    
    public static  Map<String, Object> sumAndMerge(Map<String, Object>... maps)
    {
        return sumAndMerge(Arrays.asList(maps));
    }
    
    static <E> E clone(E e)
    {
        return (E) Cloner.clone(e);
    }
    
    private static class Cloner
    {
        private static final Class<?>[] VALUE_ACCEPT_CLASSES = new Class<?>[]
            {
                String.class,
                Number.class,
                Boolean.class,
            };
        
        public static Object clone(Object o)
        {
            if (o instanceof Map)
            {
                Map<?, ?> m = (Map) o;
                Map<Object, Object> clone = new HashMap(m.size());
                
                for (Map.Entry<? extends Object, ? extends Object> entrySet : m.entrySet()) {
                    Object key = entrySet.getKey();
                    Object value = entrySet.getValue();
                    
                    clone.put(key, clone(value));
                }
                
                return clone;
            }
            
            boolean isAccept = false;
            for (Class<?> cl : VALUE_ACCEPT_CLASSES) {
                if (cl.isAssignableFrom(o.getClass()))
                {
                    isAccept = true;
                    break;
                }
            }

            if (!isAccept)
            {
                throw new IllegalArgumentException("Not accepted value class ["
                    + o.getClass() + "]! Accepted: " + Arrays.toString(VALUE_ACCEPT_CLASSES));
            }

            return o;
        }
    }
}
